信号分析
首先我们对这个信号进行 fft,观察它的频率成分,代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import scipy.signal as signal import numpy as np import matplotlib.pyplot as plt
'''首先分析信号的频谱'''
fs = 20 ts = 1 / fs Ts = 4 N = int(Ts / ts)
t = np.linspace(0, Ts, N) ft = np.sin(2 * np.pi * t) + (1 / 3) * np.sin(6 * np.pi * t)
freqx = np.fft.fftfreq(N, d=1 / fs) fft_vals = np.fft.fft(ft) fft_theo = 2.0 * np.abs(fft_vals / N)
plt.figure(1) plt.subplot(211) plt.plot(t, ft) plt.title(u'信号') plt.xlabel(u'时间(s)')
plt.subplot(212) plt.plot(freqx, fft_theo) plt.title(u'信号的频谱') plt.xlabel(u'频率(Hz)')
|
滤波器设计
我们可以发现,这个信号的有两个频率成分,1 Hz 和 3 Hz,我们需要设计一个低通滤波器,将 3 Hz 的成分去掉,于是我们可以令通带边缘频率为 1.5 Hz,阻带起始频率为 2.5 Hz,通常为了计算方便,利用采样频率将频率值归一化,即将它们除以采样频率,参数计算如下:
- 采样频率 $f_s = 20Hz$
- 通带边缘频率 $f_p = \frac{1.5Hz}{f_s}=0.075Hz$
- 通带边缘频率 $f_p = \frac{2.5Hz}{f_s}=0.125Hz$
- 通带边缘角频率 $w_p =2 \pi*f_p=0.15\pi \ [rad/s]$
- 通带波纹 $\delta_p=0.1$
- 阻带起始角频率 $w_s= 2\pi * f_s = 0.25 \pi \ [rad/s]$
- 阻带波纹 $\delta_s=0.01$
- 截止角频率 $ w_c = \frac{\omega_{p}+\omega_{s}}{2}=0.2 \pi$
下面计算阻带衰减:
最后可以选用汉明窗,计算窗长:
理想低通滤波器的时域理想脉冲响应为:
下面我们将时域的理想脉冲响应加上窗得到滤波器的时域脉冲响应,并画出滤波器的频域响应:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| '''开始设计滤波器除去 3 Hz 的成分'''
def h_ideal(N_w, wc): n = np.arange(-N_w/2, N_w/2, 1) respond = (wc/np.pi) * np.sinc((wc/np.pi) * n) return respond
wc = 0.2 * np.pi N_w = 66 h_ideal = h_ideal(N_w, wc) h_filter = h_ideal * np.hamming(66)
plt.figure(2)
plt.subplot(311) n = np.arange(-N_w/2, N_w/2, 1)*ts plt.stem(n, h_filter, '-') plt.title(u'滤波器的脉冲响应') plt.xlabel(u'时间(s)')
plt.subplot(312) w1, h1 = signal.freqz(h_ideal) plt.plot(w1/(2 * np.pi), 20*np.log10(np.abs(h1))) plt.title(u'不加窗的频率响应') plt.xlabel(u'归一化(除以采样频率)后的频率(Hz)') plt.ylabel(u"幅值(dB)")
plt.subplot(313) w2, h2 = signal.freqz(h_filter) plt.plot(w2/(2 * np.pi), 20*np.log10(np.abs(h2))) plt.title(u'加窗后的频率响应') plt.xlabel(u'归一化(除以采样频率)后的频率(Hz)') plt.ylabel(u"幅值(dB)")
plt.show()
|
freqz用于计算数字滤波器的频率响应,它的调用方式如下:
freqz(b, a=1, worN=None, whole=0, plot=None)
其中b和a是滤波器的系数,b 是滤波器的时域响应 h_filter,worN为所计算的频率点数,whole为0表示计算频率的上限为pi,whole为1表示计算频率的上限为2*pi。
它返回一个组元 (w,h) ,其中w为所有计算了响应的频率数组,其值为归一化的角频率,因此通过w/(2*pi)可以计算出对应的正规化频率。h是一个复数数组,它表示滤波器系统在每个对应的频率点的响应。复数的幅值表示滤波器的增益特性,相角表示滤波器的相位特性。
参考:http://bigsec.net/b52/scipydoc/filters.html
firwin(N, cutoff, width=None, window='hamming')
其中 N 为滤波器的长度,cutoff 是归一化后的的频率,window 为所使用的窗函数。
得到的结果如下:
注意此处 $w_c$ 经过了归一化,所以得到的频谱也是经过归一化的频率值,所以过渡带介于 0.075 和 0.125 之间。
进行滤波
上面我们得到滤波器的脉冲响应 h_filter,下面将我们的信号与其做卷积,得到信号通过滤波器的响应:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| '''下面将信号与设计的滤波器脉冲响应进行卷积'''
ft_filtered = signal.lfilter(h_filter, 1, ft)
plt.figure(3) plt.subplot(211) plt.plot(t, ft_filtered) plt.title(u'经过滤波器的后的信号') plt.xlabel(u'时间(s)')
freqx = np.fft.fftfreq(N, d= ts) fft_vals = np.abs(np.fft.fft(ft_filtered))*2 / N plt.subplot(212) plt.stem(freqx, fft_vals, '-') plt.title(u'经过滤波器后信号的频谱') plt.xlabel(u'频率(Hz)')
plt.tight_layout() plt.subplots_adjust(wspace=0.4, hspace=0.4) plt.show()
|
signal.lfilter(b, a, x, axis=-1, zi=None)
此函数可以让信号通过滤波器,实现信号与滤波器脉冲响应的卷积,其中的 b 和 a 是滤波器的系数,x 是输入信号。
由于信号刚进入滤波器进行卷积时会产生延迟,所以得到的结果如下:
为了得到更准确的频谱,将前 40 个点去掉再做 fft:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ft_filtered = signal.lfilter(h_filter, 1, ft)
ft_filtered_clip = ft_filtered[40:] t_clip = t[40:] N_clip = len(t_clip)
plt.figure(3) plt.subplot(211) plt.plot(t_clip, ft_filtered_clip) plt.title(u'经过滤波器的后的信号') plt.xlabel(u'时间(s)')
freqx = np.fft.fftfreq(N_clip, d= ts) fft_vals = np.abs(np.fft.fft(ft_filtered_clip))*2 / N_clip plt.subplot(212) plt.stem(freqx, fft_vals, '-') plt.title(u'经过滤波器后信号的频谱') plt.xlabel(u'频率(Hz)')
|
结果为:
我们可以发现,3 Hz 的成分已经被滤去,只剩 1Hz 的低频部分,滤波成功!
总结
总而言之,设计滤波器的最终目的是获得滤波器系数 b 和 a,在 python 的库 scipy.signal
中有非常多方法可以简单获得 b,a,例如:
1 2
| b, a = signal.butter(4, 100, 'low', analog=True) b = signal.remez(length, (0, 0.18, 0.2, 0.50), (0.01, 1))
|
其中 b 就相当于滤波器的时域脉冲响应 h_fliter.
得到滤波器系数后就可以使用 signal.lfilter(b, a, x, axis=-1, zi=None)
对信号进行滤波了,也可以用 freqz(b, a=1, worN=None, whole=0, plot=None)
作出滤波器的频域响应。
下面是一个例子,更多例子见这个网站。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import scipy.signal as signal import numpy as np import pylab as pl
for length in [11, 31, 51, 101, 201]: b = signal.remez(length, (0, 0.18, 0.2, 0.50), (0.01, 1)) w, h = signal.freqz(b, 1) pl.plot(w/2/np.pi, 20*np.log10(np.abs(h)), label=str(length)) pl.legend() pl.xlabel(u"正规化频率 周期/取样") pl.ylabel(u"幅值(dB)") pl.title(u"remez设计高通滤波器 - 滤波器长度和频响的关系") pl.show()
|
参考文章